home *** CD-ROM | disk | FTP | other *** search
- #!/usr/local/bin/gawk -f
- #!/usr/bin/awk -f
- # @(#) uucon.gawk 2.1 97/01/08
- # 91/04/19 john h. dubois iii (john@armory.com)
- # 91/06/30 Replaced * with + in split pattern (awk didn't care but gawk does)
- # 91/08/04 Added separate NOT_POLLED_WARN parameter
- # 91/08/05 Made awk script exit if no files found; added debug option
- # 91/12/31 Moved finds inside of awk program; made awk program a separate file
- # since this change made it larger than a command line can be
- # 92/03/03 Removed hardcoding of path to awk so it will work under UNIX
- # 92/03/06 Fixed error initializing last-login file
- # Don't exit if no files found if debugging is on
- # Don't use first field of last-login line
- # 92/03/10 Changed "startup" message searched for so that it will work with
- # both XENIX and UNIX
- # 92/05/02 Fixed check for files waiting for polling systems
- # Changed to be a #!awk script
- # 92/12/06 Print warnings if poll file or systems file can't be opened,
- # make sure track files are readable by all
- # 92/12/17 Added -n and -l options
- # 93/01/25 Changed from using last success startup line to using last
- # successful conversation line;
- # correctly note lack of succesful conversation line with -n & -l
- # 93/01/28 Cleaned up/bugfixes
- # 93/05/28 Added sorting by last connect time. Doesn't work across year
- # ends because logfiles don't contain year.
- # 93/11/10 Force WePoll[] to be an array
- # 93/11/15 Force LastConv[] to be an array
- # 94/03/09 Use gawk so - options can be given
- # 94/03/12 Close lots of files & commands. Avoid cding to nonexistant dirs.
- # 96/07/04 Cleaned up option processing. Added andcpurslL options.
- # Let system names be given on command line. More error checking.
- # Make dates from earlier years sort correctly. Added rcfile.
- # Truncate system names to 7 chars, because uucico does when creating
- # logfiles.
- # 96/07/07 Print incoming UUCP/TCP times in local time. Added t option.
- # 96/11/05 Exit if can't read Systems
- # 97/01/08 Made -l and -L sorting work correctly.
-
- BEGIN {
- Name = "uucon"
- LogDir = "/usr/spool/uucp/.Log/uucico"
- LCDir = LogDir
- Usage = \
- "Usage: " Name " [-ahlLnurst] [-x<debug-leve>] [-c<no-con-warning-period>]\n"\
- " [-p<not-polled-warning-period>] [-d<lastcon-dir>] [system ...]"
- rcFiles = "/etc/default/uucon"
- ARGC = Opts(Name,Usage,"ac>p>d:rstlLuhx<",0,rcFiles,
- "ALL,NO_CON_WARN,NOT_POLLED_WARN,LC_DIR,RSORT,SITESORT,NOCONVERT",0,"n")
-
- debug = Options["x"]
- if (debug)
- print "debugging is on."
- if ("h" in Options) {
- help(Name,Usage,LCDir,rcFiles)
- exit(0)
- }
-
- if ("d" in Options)
- LCDir = Options["d"]
- toLocal = !("t" in Options)
-
- if (SitesGiven = (ARGC > 1)) {
- for (i = 1; i < ARGC; i++)
- Systems[ARGV[i]]
- if (debug)
- printf "%d sitename(s) given.\n",ARGC-1 > "/dev/stderr"
- }
- All = "a" in Options
- if (Options["L"] || Options["l"]) { # Print last-conversation lines
- Cmd = "uuname"
- while ((Cmd | getline System) == 1)
- allSystems[substr(System,1,7)]
- close(Cmd)
- CheckSysList(Systems,allSystems,SitesGiven,Name)
- # Report last conv with all systems
- DoLast(Options["l"],LogDir,LCDir,Systems,"r" in Options,
- "s" in Options,allSystems,All)
- exit(0)
- }
-
- # Read poll file and Systems file
- split("",WePoll,"") # make awk realize this is an array
- if (!Setup("/usr/lib/uucp/Poll.hour",allSystems,WeCall,Never,WePoll))
- exit 1
- CheckSysList(Systems,allSystems,SitesGiven,Name)
-
- # Get/save last-conv lines
- split("",LastConv,"") # convince awk this is an array
- FindLastConv(LogDir,Systems,LastConv) # read uucico logs
- if ("u" in Options)
- SaveLastConv(LCDir,LastConv) # write last-conv files
- ReadLastConv(LCDir,Systems,LastConv,allSystems) # read last-conv files
- if (All)
- CopySet(Systems,LastConv)
-
- # Set NoConSystems and WarnSystems according to dates on last-conv files
- # Convince awk that these are arrays
- split("",NoConSystems)
- split("",NotPolledSystems)
-
- if ("c" in Options)
- NoConWarn = Options["c"]
- else
- NoConWarn = 1
- if ("p" in Options)
- NotPolledWarn = Options["p"]
- else
- NotPolledWarn = NoConWarn
- FindOldFiles(NoConSystems,NotPolledSystems,NoConWarn,NotPolledWarn)
- ReportNoConv(LastConv,NoConSystems,NotPolledSystems,WeCall,WePoll)
- }
-
- # Complain about any systems in Systems[] that are not also in allSystems[].
- # If no systems given, copy allSystems[] to Systems[].
- function CheckSysList(Systems,allSystems,SitesGiven,Name, sys) {
- if (SitesGiven) {
- for (sys in Systems)
- if (!(sys in allSystems))
- printf \
- "%s: system \"%s\" does not exist in the Systems file.\n",
- Name,sys > "/dev/stderr"
- }
- else
- CopyArr(allSystems,Systems)
- if (debug) {
- print "Final systems list:" > "/dev/stderr"
- for (sys in Systems)
- print sys > "/dev/stderr"
- print "" > "/dev/stderr"
- }
- }
-
- # Report systems that there has been no successful conversation with for
- # more than the configured number of days.
- # A warning is printed only for systems that: are polled and are in
- # NoConSystems; are called on demand and have jobs waiting and are in
- # NoConSystems; or poll us and have jobs waiting and are in NotPolledSystems.
- # A system is in:
- # WeCall if it has a Systems line that has a call time other than Never.
- # WePoll if it is a system we poll.
- # NoConSystems if it has a last-conv file at least NoConWarn days old.
- # NotPolledSystems if it has a last-conv file at least NotPolledWarn days old.
- #
- # LastConv contains the last-conv line for each system, for use in warnings.
- # It contains an entry for every system in the UUCP configuration.
- function ReportNoConv(LastConv,NoConSystems,NotPolledSystems,WeCall,WePoll,
- NumFiles,System,Elem,Cmd,When,Warn,SysList,Line,SysType) {
- # Make SysType array so awk will not complain later if nothing is put in it
- split("",SysType)
- for (System in LastConv) {
- if ((System in NoConSystems) && (System in WePoll))
- SysType[System] = "polled "
- else if ((System in NoConSystems) && (System in WeCall))
- SysType[System] = "called "
- else if ((System in NotPolledSystems) && !(System in WeCall))
- SysType[System] = "polling "
- else if (debug) {
- printf "Systems %s: no warning.\n",System
- continue
- }
- if (debug)
- printf "Systems %s is %s\n",System,SysType[System]
- }
- for (System in SysType)
- SysList = SysList System " "
- if (SysList == "")
- return
- # Check for waiting files.
- Cmd = "for system in " SysList "; do dir=/usr/spool/uucp/$system;"\
- "[ ! -x $dir ] && continue; cd $dir; set -- *;"\
- "[ $1 != '*' ] && echo $system $#; done"
- while ((Cmd | getline Line) == 1) {
- split(Line,Elem)
- NumFiles[Elem[1]] = Elem[2]
- }
- close(Cmd)
- for (System in SysType) {
- if (!(System in WePoll) && !NumFiles[System])
- continue
- split(LastConv[System],Elem)
- if (Elem[3] == "NEVER")
- When = "EVER"
- else
- When = "since " substr(Elem[3],2,index(Elem[3],",") - 2)
- printf \
- "No successful conversation with %ssystem %s %s;\n%d waiting files.\n",
- SysType[System],Elem[2],When,NumFiles[System]
- }
- }
-
- # Sets NoConSystems to systems with a last-conv file more than NoConWarn
- # days old, and NotPolledSystems to systems with a last-conv file more than
- # NotPolledWarn days old.
- function FindOldFiles(NoConSystems,NotPolledSystems,NoConWarn,NotPolledWarn,
- File) {
- # Get lists of .system files that were last
- # modified at warning threshold or longer ago
- # Set NoConList to last-conv files more than NoConWarn days old, and
- # NotPolledList to last-conv files more than NotPolledWarn days old.
- if (!(FilesByDate(LogDir,".??*","+" NoConWarn,NoConList) + \
- FilesByDate(LogDir,".??*","+" NotPolledWarn,NotPolledList)) && !debug)
- return # quit if no files found
-
- if (debug)
- printf "Last-login files older than NoConWarn (%d) day(s): ",
- NoConWarn
- for (File in NoConList) {
- if (debug)
- printf "%s ",File
- # get rid of leading .
- NoConSystems[substr(File,2)]
- }
- if (debug)
- print ""
- if (debug)
- printf "Last-login files older than NotPolledWarn (%d) day(s): ",
- NotPolledWarn
- for (File in NotPolledList) {
- if (debug)
- printf "%s ",File
- NotPolledSystems[substr(File,2)]
- }
- if (debug)
- print ""
- }
-
- # Report last connection to systems.
- function DoLast(Long,LogDir,LCDir,Systems,ReverseSort,SiteSort,allSystems,All,
- System,LastConv,E,NFields,Time,NSys,Cmd,Month,Day,Hour,Minute,Second,CurYear,
- LastYear,Date,tty,Offset) {
- # Get info from uucico logs
- FindLastConv(LogDir,Systems,LastConv)
- # Get info from last-conv status files
- ReadLastConv(LCDir,Systems,LastConv,allSystems)
- if (All)
- CopySet(Systems,LastConv)
- # Expected format of .systemname files:
- #1 2 3 4 5 6 7 8 9 10 11 12 13 14
- #uucp gorn (1/25-16:28:20,17495,0) OK (conversation complete tty3C 1)
- # month:3 day:4 hour:5 minute:6 second:7 tty:13
- CurYear = strftime("%y")
- LastYear = sprintf("%02d",CurYear - 1)
- MakeTZOffset()
- if (debug)
- printf "TZ offset is %d\n",TZOffset
- for (System in LastConv) {
- NFields = split(LastConv[System],E,"[- \t(),/:]+")
- if (NFields > 3) {
- if (NFields < 13) {
- printf "Bad last-connnection record (not enough fields):\n%s\n",
- LastConv[System] > "/dev/stderr"
- continue
- }
- tty = E[13]
- Month = E[3]+0
- Day = E[4]+0
- Hour = E[5]+0
- Minute = E[6]+0
- Second = E[7]+0
- if (Month < 1 || Month > 12 || Day < 1 || Day > 31 || Hour < 0 || \
- Hour > 23 || Minute < 0 || Minute > 60 || Second < 0 || \
- Second > 60) {
- printf "Bad date or time in last-connnection record:\n%s\n",
- LastConv[System] > "/dev/stderr"
- continue
- }
-
- Offset = (tty == "notty") ? 0 : TZOffset
- Time = unixtime(CurYear,Month,Day,Hour,Minute,Second,Offset)
- # Log files don't include year, so if the month/day put it
- # in the future, assume it was from last year.
- # Get new systime each time it is used, just in case
- # logfiles are still being written.
- if (Time > systime()) {
- Time = \
- unixtime(LastYear,Month,Day,Hour,Minute,Second,Offset)
- if (debug)
- printf "Record from previous year:\n%s\n",
- LastConv[System] > "/dev/stderr"
- }
- # SCO uucpd does not set TZ, so uucico writes logfiles in GMT.
- # Fortunately, this case can be recognized because uucico logs
- # tty as "notty". Use strftime() to convert to local time.
- # Otherwise, avoid the possibility that the above calculations
- # are incorrect by just reforatting the fields from the
- # logfile.
- if (toLocal && tty == "notty")
- Date = strftime("%m/%d %T",Time)
- else
- Date = sprintf("%02d/%02d %02d:%02d:%02d",Month,Day,Hour,
- Minute,Second)
- if (Long)
- PrintVal[System] = sprintf("%-8s %s %s",System,Date,tty)
- else
- PrintVal[System] = LastConv[System]
- if (debug)
- printf "Last login for %s was %s\n",System,
- strftime("%y/%m/%d %T",Time) > "/dev/stderr"
- }
- else {
- if (Long)
- PrintVal[System] = sprintf("%-8s NEVER",System)
- else
- PrintVal[System] = \
- sprintf("---- %s No successful conversations",System)
- Time = 0
- }
- if (SiteSort)
- SortVal[System] = System
- else
- SortVal[System] = sprintf("%010d",Time)
- if (debug)
- printf "Sort key for %s is: %s\n",System,SortVal[System] \
- > "/dev/stderr"
- }
- if (debug)
- printf "Sorting...\n" > "/dev/stderr"
- NSys = qsortArbIndByValue(SortVal,k)
- print "System Last connection TTY"
- if (ReverseSort)
- for (i = NSys; i >= 1; i--)
- print PrintVal[k[i]]
- else
- for (i = 1; i <= NSys; i++)
- print PrintVal[k[i]]
- }
-
- function help(Name,Usage,LCDir,rcFile) {
- printf \
- "%s: check whether remote UUCP systems have been successfully connected to.\n"\
- "%s\n"\
- " A warning is printed for systems that have not been connected to in\n"\
- "a given period, by default one day. This warning is printed only for\n"\
- "systems that are polled (called regularly regardless of whether there are\n"\
- "jobs) or which have UUCP jobs waiting; the warning is not printed for\n"\
- "systems that have no jobs waiting and which are never called or only\n"\
- "called on demand.\n"\
- " A similar warning is also for systems that must poll us (systems that\n"\
- "are never called) that have jobs waiting. By default, the no-connection\n"\
- "period is used for this warning, but a different value may be given with\n"\
- "-p; it might be set higher than the no-connection warning period as\n"\
- "failure to poll may be a common occurance and of less concern.\n"\
- " %s searches the uucico logs for successful conversation lines.\n"\
- "If no successful conversation line is found for a system, it is read from\n"\
- "its last-conversation file, if it exists.\n"\
- "Options:\n"\
- "Some of the following options can also be set by assigning values to\n"\
- "variables in the configuration file %s. Variables are\n"\
- "assigned to with the syntax: varname=value or in the case of flags, by\n"\
- "simply putting the indicated variable name in the file without a value.\n"\
- "Flag options can be turned off on the command line by following them\n"\
- "immediately with '-', e.g. -r- to turn off the r option in such a way that\n"\
- "it cannot be turned on in the config file. Variable names appear in\n"\
- "parentheses in the option descriptions.\n"\
- "-a: Print warnings for or list last logins for all systems, including\n"\
- " those who poll us and have no jobs queued up for them. (ALL)\n"\
- "-u: The last-conversation file for each system that a successful\n"\
- " conversation line is found for is udpated. The successful\n"\
- " conversation line is written to its last-conversation file,\n"\
- " %s/.<system-name>\n"\
- " %s should generally be run with the -u option once a day, immediately\n"\
- " before the uucico logfiles are cleaned up (for example, by putting\n"\
- " \"uucon -u\" near the top of the uudemon.clean script). This allows\n"\
- " long-term tracking of the last successful connection to a system.\n"\
- "-x<debug-level>: Turn on debugging output at level <debug-level>. Higher\n"\
- " values produce more output.\n"\
- "-t: By default, logfile times for incoming UUCP/TCP connections are taken\n"\
- " to be in GMT (because SCO uucpd does not set TZ), and are converted to\n"\
- " local time. If -t is given, this conversion is not done. (NOCONVERT)\n"\
- "-h: Print this help.\n"\
- "-L: Print last-conversation lines from the uucico logs or last-conversation\n"\
- " files for all systems. No warnings are printed, and no attempt is\n"\
- " made to read the Systems file or to write to last-conversation files.\n"\
- " Lines are printed in order from oldest to most recent connection.\n"\
- "-l: Like -L except that a header is printed and the lines are printed in\n"\
- " a more readable format.\n"\
- "-s: Sort -l and -L output by site name. (SITESORT)\n"\
- "-r: Print -l and -L output in reverse order. (RSORT)\n"\
- "-c<no-con-warning-period>: Set the no-connection warning period to\n"\
- " <no-con-warning-period> days. This also set the not-polled warning\n"\
- " period, unless -p is also given. (NO_CON_WARN)\n"\
- "-p<not-polled-warning-period>: Set the not-polled warning period to\n"\
- " <not-polled-warning-period> days. (NOT_POLLED_WARN)\n"\
- "-d<log-dir>: Store last-conversation files for each system in <log-dir>\n"\
- " instead of the default directory %s. (LC_DIR)\n"\
- "-n: Do not read the configuration file (%s).\n",
- Name,Usage,Name,rcFile,LCDir,Name,LCDir,rcFile
- }
-
- function GetLastFiles(LCDir,LastConvFiles,allSystems,
- Cmd,LogFiles,Num,Elem,i,System) {
- Cmd = "cd " LCDir "; echo .??*"
- Cmd | getline LogFiles
- close(Cmd)
- Num = split(LogFiles,Elem," ")
- for (i = 1; i <= Num; i++) {
- System = Elem[i]
- if (System == ".??*" || System == "")
- continue
- if (debug)
- print "File found in last-conv dir: " System > "/dev/stderr"
- System = substr(System,2)
- if (!(System in allSystems))
- printf \
- "%s: Warning: In %s, found last-conversation file\n"\
- "for a system that does not exist in the Systems file: %s\n",
- Name,LCDir,System
- else
- LastConvFiles[System]
- }
- }
-
- # Read last-successful-conversation files of systems that do not have
- # a successful-conversation line in their uucico logfiles.
- function ReadLastConv(LCDir,Systems,LastConv,allSystems,
- File,System,LastConvFiles) {
- # Find what last conv files exist, party to make sure there are not any
- # obsolete files and party so we know whether to complain about files that
- # cannot be opened.
- GetLastFiles(LCDir,LastConvFiles,allSystems)
- for (System in Systems) {
- if ((!(System in LastConv) || LastConv[System] == "") &&
- System in LastConvFiles) {
- if (debug)
- printf "Reading last-successful-conversation file for %s\n",
- System
- File = LCDir "/." System
- if ((ret = (getline LastConv[System] < File)) != 1)
- printf \
- "Could not open status file \"%s\":\ngetline returned %d.\n",
- File,ret
- close(File)
- }
- else if (debug)
- printf "Found successful-conversation line in uucico log for %s;\n"\
- "skipping status file\n",System
- }
- }
-
- # Finds files that match Pattern and were last modified Days days
- # ago (a number optionally preceded by + or - as in find(C))
- # A list of the files is put in List
- # The number of matching files found is returned
- function FilesByDate(Dir,Pattern,Days,List, num) {
- num = 0
- FindCmd = "cd " Dir "; find " Pattern " -mtime " Days " -print 2>&1"
- while ((FindCmd | getline FileName) == 1)
- if (FileName !~ "find: stat.. failed") {
- List[FileName]
- num++
- }
- close(FindCmd)
- return num
- }
-
- # Sets these:
- # WeCall: systems we call, from Systems
- # Never: systems we never call, from Systems
- # WePoll: systems we poll, from poll file
- # Systems in poll file that are not in Systems file or are listed
- # as Never called in Systems file are not included in WePoll.
- # Systems: all systems listed in Systems file
- function Setup(PollFile,Systems,WeCall,Never,WePoll,
- result,SystemsFile,Sysfiles,SysList,System) {
- # SysList: all systems listed in uucp config, from poll file & Systems file
- # read Sysfiles records & get name of uucico systems file
- if (ReadRecFile("/usr/lib/uucp/Sysfiles",Sysfiles) != -1)
- SystemsFile = FindSystemsFile(Sysfiles)
- if (SystemsFile == "") {
- if (debug)
- print \
- "No Sysfiles file; systems file is /usr/lib/uucp/Systems."
- SystemsFile = "/usr/lib/uucp/Systems"
- }
- # Make WeCall[] indexes be the set of systems we call
- while ((result = (getline < PollFile)) == 1)
- if (($0 !~ "^#") && NF) {
- System = substr($1,1,7)
- WePoll[System]
- SysList[System]
- }
- close(PollFile)
- if (result < 0)
- printf "Warning: error reading poll file \"%s\".\n",PollFile
- while ((result = (getline < SystemsFile)) == 1) {
- if (($0 ~ "^#") || !NF) # ignore empty & comment lines
- continue
- System = substr($1,1,7)
- if ($2 ~ "[nN]ever")
- Never[System]
- else
- WeCall[System]
- SysList[System]
- Systems[System]
- }
- close(SystemsFile)
- if (result < 0) {
- printf "Error reading Systems file \"%s\".\n",SystemsFile
- return 0
- }
- if (debug) {
- printf "Systems we poll: "
- for (Sys in WePoll)
- printf Sys " "
- printf "\nSystems we never call: "
- for (Sys in Never)
- printf Sys " "
- printf "\nSystems we call: "
- for (Sys in WeCall)
- printf Sys " "
- print ""
- for (Sys in SysList)
- if (Sys in WePoll) {
- if (Sys in Never)
- printf \
- "Warning: System \"%s\" exists in Poll file %s,\n"\
- " but is listed as being never called in systems file %s.\n",
- Sys,PollFile,SystemsFile
- else if (Sys in WeCall)
- printf \
- "System \"%s\" exists in Poll file %s,\n"\
- " and is listed as being called in systems file %s.\n",
- Sys,PollFile,SystemsFile
- else
- printf \
- "Warning: System \"%s\" exists in Poll file %s,\n"\
- " but has no entry in Systems file \"%s\".\n",
- Sys,PollFile,SystemsFile
- }
- else {
- if (Sys in Never)
- printf \
- "System \"%s\" does not exist in Poll file %s,\n"\
- " and is listed as being never called in systems file %s.\n",
- Sys,PollFile,SystemsFile
- else if (Sys in WeCall)
- printf \
- "System \"%s\" does not exist in Poll file %s,\n"\
- " but is listed as being called in systems file %s.\n",
- Sys,PollFile,SystemsFile
- }
- }
- for (System in WePoll)
- if (!(System in WeCall))
- delete WePoll[System]
- return 1
- }
-
- # read File into Arr (one record per element of Arr).
- # Records are lines which are continued onto the next
- # line if they end with \.
- function ReadRecFile(File,Arr,result,i,line)
- {
- i = -1
- while ((result = (getline line < File)) == 1) {
- if (line ~ "^#")
- continue
- if (Arr[i] ~ /\\[ \t]*$/) {
- sub("\\\\[ \t]*$"," ",Arr[i])
- Arr[i] = Arr[i] line
- }
- else
- Arr[++i] = line
- }
- close(File)
- return result
- }
-
- # Find uucico record in arr generated from Systems file,
- # and return the value of its systems field.
- function FindSystemsFile(Sysfiles,i,tok,file,elem,var,systemsfile)
- {
- for (i in Sysfiles) {
- split(Sysfiles[i],tok,"[ \t]+")
- var["service"] = ""
- var["systems"] = "Systems"
- for (file in tok) {
- split(tok[file],elem,"=")
- var[elem[1]] = elem[2]
- }
- if (var["service"] == "uucico") {
- systemsfile = var["systems"]
- if ( systemsfile !~ "^/" )
- systemsfile = "/usr/lib/uucp/" systemsfile
- return systemsfile
- }
- }
- return ""
- }
-
- # Find last successful connection to sites
- # by searching their log files for successful-conversation lines.
- function FindLastConv(LogDir,Systems,LastConv,
- Cmd,LogFiles,Names,System,Line,count,Elem,i,Num) {
- Cmd = "cd " LogDir "; echo *"
- Cmd | getline LogFiles
- close(Cmd)
- Num = split(LogFiles,Elem," ")
- for (i = 1; i <= Num; i++) {
- System = Elem[i]
- if (System == "*" || System == "")
- continue
- if (debug)
- print "File found in log dir: " System > "/dev/stderr"
- Names[System]
- count++
- }
- if (!count) {
- if (debug)
- printf "No log files found in %s.\n",LogDir
- return
- }
-
- # for each system that has a log file...
- if (debug)
- printf "Reading logfiles: "
- for (System in Systems)
- if (System in Names) {
- if (debug)
- printf "%s ",System
- LastConv[System]
- LogFile = LogDir "/" System
- while ((getline Line < LogFile) == 1)
- if (Line ~ /OK \(conversation/)
- LastConv[System] = Line
- close(LogFile)
- }
- if (debug)
- print ""
- }
-
- # Writes last successful conversation lines for systems to a last-conversation
- # file. If the last conversation line is empty, meaning that the system had a
- # logfile but no successful-conversation line was found, and does not already
- # have a last-conversation file, an initial last-sucessful-conversation file is
- # created for it anyway.
- function SaveLastConv(LCDir,LastConv,
- System,TrackFile,TrackFiles) {
- if (debug)
- print "Writing logfiles..."
- for (System in LastConv) {
- TrackFile = LogDir "/." System
- if (LastConv[System] != "") {
- if (debug)
- printf "Last conversation line for system \"%s\":\n%s\n",
- System, LastConv[System]
- print LastConv[System] > TrackFile
- TrackFiles = TrackFiles " " TrackFile
- }
- else if ((getline < TrackFile) != 1) {
- # if no last-successful-conversation file...
- if (debug)
- printf "No conversation line found for system \"%s\"\n",File
- print "uucp " System " NEVER" > TrackFile
- TrackFiles = TrackFiles " " TrackFile
- }
- close(TrackFile)
- }
- if (TrackFiles != "")
- system("chmod a+r" TrackFiles)
- }
-
- ### Start of ProcArgs library
- # @(#) ProcArgs 1.11 96/12/08
- # 92/02/29 john h. dubois iii (john@armory.com)
- # 93/07/18 Added "#" arg type
- # 93/09/26 Do not count -h against MinArgs
- # 94/01/01 Stop scanning at first non-option arg. Added ">" option type.
- # Removed meaning of "+" or "-" by itself.
- # 94/03/08 Added & option and *()< option types.
- # 94/04/02 Added NoRCopt to Opts()
- # 94/06/11 Mark numeric variables as such.
- # 94/07/08 Opts(): Do not require any args if h option is given.
- # 95/01/22 Record options given more than once. Record option num in argv.
- # 95/06/08 Added ExclusiveOptions().
- # 96/01/20 Let rcfiles be a colon-separated list of filenames.
- # Expand $VARNAME at the start of its filenames.
- # Let varname=0 and -option- turn off an option.
- # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
- # of the vars should be searched for in the environment.
- # Check for duplicate rcfiles.
- # 96/05/13 Return more specific error values. Note: ProcArgs() and InitOpts()
- # now return various negatives values on error, not just -1, and
- # Opts() may set Err to various positive values, not just 1.
- # Added AllowUnrecOpt.
- # 96/05/23 Check type given for & option
- # 96/06/15 Re-port to awk
- # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
- # used by other functions.
- # 96/10/15 Added OptChars
- # 96/11/01 Added exOpts arg to Opts()
- # 96/11/16 Added ; type
- # 96/12/08 Added Opt2Set() & Opt2Sets()
- # 96/12/27 Added CmdLineOpt()
-
- # optlist is a string which contains all of the possible command line options.
- # A character followed by certain characters indicates that the option takes
- # an argument, with type as follows:
- # : String argument
- # ; Non-empty string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # The only difference the type of argument makes is in the runtime argument
- # error checking that is done.
-
- # The & option is a special case used to get numeric options without the
- # user having to give an option character. It is shorthand for [-+.0-9].
- # If & is included in optlist and an option string that begins with one of
- # these characters is seen, the value given to "&" will include the first
- # char of the option. & must be followed by a type character other than ":"
- # or ";".
- # Note that if e.g. &> is given, an option of -.5 will produce an error.
-
- # Strings in argv[] which begin with "-" or "+" are taken to be
- # strings of options, except that a string which consists solely of "-"
- # or "+" is taken to be a non-option string; like other non-option strings,
- # it stops the scanning of argv and is left in argv[].
- # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
- # If an option takes an argument, the argument may either immediately
- # follow it or be given separately.
- # "-" and "+" options are treated the same. "+" is allowed because most awks
- # take any -options to be arguments to themselves. gawk 2.15 was enhanced to
- # stop scanning when it encounters an unrecognized option, though until 2.15.5
- # this feature had a flaw that caused problems in some cases. See the OptChars
- # parameter to explicitly set the option-specifier characters.
-
- # If an option that does not take an argument is given,
- # an index with its name is created in Options and its value is set to the
- # number of times it occurs in argv[].
-
- # If an option that does take an argument is given, an index with its name is
- # created in Options and its value is set to the value of the argument given
- # for it, and Options[option-name,"count"] is (initially) set to the 1.
- # If an option that takes an argument is given more than once,
- # Options[option-name,"count"] is incremented, and the value is assigned to
- # the index (option-name,instance) where instance is 2 for the second occurance
- # of the option, etc.
- # In other words, the first time an option with a value is encountered, the
- # value is assigned to an index consisting only of its name; for any further
- # occurances of the option, the value index has an extra (count) dimension.
-
- # The sequence number for each option found in argv[] is stored in
- # Options[option-name,"num",instance], where instance is 1 for the first
- # occurance of the option, etc. The sequence number starts at 1 and is
- # incremented for each option, both those that have a value and those that
- # do not. Options set from a config file have a value of 0 assigned to this.
-
- # Options and their arguments are deleted from argv.
- # Note that this means that there may be gaps left in the indices of argv[].
- # If compress is nonzero, argv[] is packed by moving its elements so that
- # they have contiguous integer indices starting with 0.
- # Option processing will stop with the first unrecognized option, just as
- # though -- was given except that unlike -- the unrecognized option will not be
- # removed from ARGV[]. Normally, an error value is returned in this case.
- # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
- # be found, so the number of remaining arguments is returned instead.
- # If OptChars is not a null string, it is the set of characters that indicate
- # that an argument is an option string if the string begins with one of the
- # characters. A string consisting solely of two of the same option-indicator
- # characters stops the scanning of argv[]. The default is "-+".
- # argv[0] is not examined.
- # The number of arguments left in argc is returned.
- # If an error occurs, the global string OptErr is set to an error message
- # and a negative value is returned.
- # Current error values:
- # -1: option that required an argument did not get it.
- # -2: argument of incorrect type supplied for an option.
- # -3: unrecognized (invalid) option.
- function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
- ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
- NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
- {
- # ArgNum is the index of the argument being processed.
- # ArgsLeft is the number of arguments left in argv.
- # Arg is the argument being processed.
- # ArgLen is the length of the argument being processed.
- # ArgInd is the position of the character in Arg being processed.
- # Option is the character in Arg being processed.
- # Pos is the position in OptList of the option being processed.
- # NumOpt is true if a numeric option may be given.
- ArgsLeft = argc
- NumOpt = index(OptList,"&")
- OptionNum = 0
- if (OptChars == "")
- OptChars = "-+"
- while (OptChars != "") {
- c = substr(OptChars,1,1)
- OptChars = substr(OptChars,2)
- OptCharSet[c]
- OptTerm[c c]
- }
- for (ArgNum = 1; ArgNum < argc; ArgNum++) {
- Arg = argv[ArgNum]
- if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
- break # Not an option; quit
- if (Arg in OptTerm) {
- delete argv[ArgNum]
- ArgsLeft--
- break
- }
- ArgLen = length(Arg)
- for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
- Option = substr(Arg,ArgInd,1)
- if (NumOpt && Option ~ /[-+.0-9]/) {
- # If this option is a numeric option, make its flag be & and
- # its option string flag position be the position of & in
- # the option string.
- Option = "&"
- Pos = NumOpt
- # Prefix Arg with a char so that ArgInd will point to the
- # first char of the numeric option.
- Arg = "&" Arg
- ArgLen++
- }
- # Find position of flag in option string, to get its type (if any).
- # Disallow & as literal flag.
- else if (!(Pos = index(OptList,Option)) || Option == "&") {
- if (AllowUnrecOpt) {
- Escape = 1
- break
- }
- else {
- OptErr = "Invalid option: " specGiven Option
- return -3
- }
- }
-
- # Find what the value of the option will be if it takes one.
- # NeedNextOpt is true if the option specifier is the last char of
- # this arg, which means that if the option requires a value it is
- # the next arg.
- if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
- if (GotValue = ArgNum + 1 < argc)
- Value = argv[ArgNum+1]
- }
- else { # Value is included with option
- Value = substr(Arg,ArgInd + 1)
- GotValue = 1
- }
-
- if (HadValue = AssignVal(Option,Value,Options,
- substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
- specGiven)) {
- if (HadValue < 0) # error occured
- return HadValue
- if (HadValue == 2)
- ArgInd++ # Account for the single-char value we used.
- else {
- if (NeedNextOpt) { # option took next arg as value
- delete argv[++ArgNum]
- ArgsLeft--
- }
- break # This option has been used up
- }
- }
- }
- if (Escape)
- break
- # Do not delete arg until after processing of it, so that if it is not
- # recognized it can be left in ARGV[].
- delete argv[ArgNum]
- ArgsLeft--
- }
- if (compress != 0) {
- dest = 1
- src = argc - ArgsLeft + 1
- for (count = ArgsLeft - 1; count; count--) {
- ARGV[dest] = ARGV[src]
- dest++
- src++
- }
- }
- return ArgsLeft
- }
-
- # Assignment to values in Options[] occurs only in this function.
- # Option: Option specifier character.
- # Value: Value to be assigned to option, if it takes a value.
- # Options[]: Options array to return values in.
- # ArgType: Argument type specifier character.
- # GotValue: Whether any value is available to be assigned to this option.
- # Name: Name of option being processed.
- # OptionNum: Number of this option (starting with 1) if set in argv[],
- # or 0 if it was given in a config file or in the environment.
- # SingleOpt: true if the value (if any) that is available for this option was
- # given as part of the same command line arg as the option. Used only for
- # options from the command line.
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Global variables: OptErr
- # Return value: negative value on error, 0 if option did not require an
- # argument, 1 if it did & used the whole arg, 2 if it required just one char of
- # the arg.
- # Current error values:
- # -1: Option that required an argument did not get it.
- # -2: Value of incorrect type supplied for option.
- # -3: Bad type given for option &
- function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
- SingleOpt,specGiven, UsedValue,Err,NumTypes) {
- # If option takes a value... [
- NumTypes = "*()#<>]"
- if (Option == "&" && ArgType !~ "[" NumTypes) { # ]
- OptErr = "Bad type given for & option"
- return -3
- }
-
- if (UsedValue = (ArgType ~ "[:;" NumTypes)) { # ]
- if (!GotValue) {
- if (Name != "")
- OptErr = "Variable requires a value -- " Name
- else
- OptErr = "option requires an argument -- " Option
- return -1
- }
- if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
- OptErr = Err
- return -2
- }
- # Mark this as a numeric variable; will be propogated to Options[] val.
- if (ArgType != ":" && ArgType != ";")
- Value += 0
- if ((Instance = ++Options[Option,"count"]) > 1)
- Options[Option,Instance] = Value
- else
- Options[Option] = Value
- }
- # If this is an environ or rcfile assignment & it was given a value...
- else if (!OptionNum && Value != "") {
- UsedValue = 1
- # If the value is "0" or "-" and this is the first instance of it,
- # do not set Options[Option]; this allows an assignment in an rcfile to
- # turn off an option (for the simple "Option in Options" test) in such
- # a way that it cannot be turned on in a later file.
- if (!(Option in Options) && (Value == "0" || Value == "-"))
- Instance = 1
- else
- Instance = ++Options[Option]
- # Save the value even though this is a flag
- Options[Option,Instance] = Value
- }
- # If this is a command line flag and has a - following it in the same arg,
- # it is being turned off.
- else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
- UsedValue = 2
- if (Option in Options)
- Instance = ++Options[Option]
- else
- Instance = 1
- Options[Option,Instance]
- }
- # If this is a flag assignment without a value, increment the count for the
- # flag unless it was turned off. The indicator for a flag being turned off
- # is that the flag index has not been set in Options[] but it has an
- # instance count.
- else if (Option in Options || !((Option,1) in Options))
- # Increment number of times this flag seen; will inc null value to 1
- Instance = ++Options[Option]
- Options[Option,"num",Instance] = OptionNum
- return UsedValue
- }
-
- # Option is the option letter
- # Value is the value being assigned
- # Name is the var name of the option, if any
- # ArgType is one of:
- # : String argument
- # ; Non-null string argument
- # * Floating point argument
- # ( Non-negative floating point argument
- # ) Positive floating point argument
- # # Integer argument
- # < Non-negative integer argument
- # > Positive integer argument
- # specGiven is the option specifier character use, if any (e.g. - or +),
- # for use in error messages.
- # Returns null on success, err string on error
- function CheckType(ArgType,Value,Option,Name,specGiven, Err,ErrStr) {
- if (ArgType == ":")
- return ""
- if (ArgType == ";") {
- if (Value == "")
- Err = "must be a non-empty string"
- }
- # A number begins with optional + or -, and is followed by a string of
- # digits or a decimal with digits before it, after it, or both
- else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
- Err = "must be a number"
- else if (ArgType ~ "[#<>]" && Value ~ /\./)
- Err = "may not include a fraction"
- else if (ArgType ~ "[()<>]" && Value < 0)
- Err = "may not be negative"
- # (
- else if (ArgType ~ "[)>]" && Value == 0)
- Err = "must be a positive number"
- if (Err != "") {
- ErrStr = "Bad value \"" Value "\". Value assigned to "
- if (Name != "")
- return ErrStr "variable " substr(Name,1,1) " " Err
- else {
- if (Option == "&")
- Option = Value
- return ErrStr "option " specGiven substr(Option,1,1) " " Err
- }
- }
- else
- return ""
- }
-
- # Note: only the above functions are needed by ProcArgs.
- # The rest of these functions call ProcArgs() and also do other
- # option-processing stuff.
-
- # Opts: Process command line arguments.
- # Opts processes command line arguments using ProcArgs()
- # and checks for errors. If an error occurs, a message is printed
- # and the program is exited.
- #
- # Input variables:
- # Name is the name of the program, for error messages.
- # Usage is a usage message, for error messages.
- # OptList the option description string, as used by ProcArgs().
- # MinArgs is the minimum number of non-option arguments that this
- # program should have, non including ARGV[0] and +h.
- # If the program does not require any non-option arguments,
- # MinArgs should be omitted or given as 0.
- # rcFiles, if given, is a colon-seprated list of filenames to read for
- # variable initialization. If a filename begins with ~/, the ~ is replaced
- # by the value of the environment variable HOME. If a filename begins with
- # $, the part from the character after the $ up until (but not including)
- # the first character not in [a-zA-Z0-9_] will be searched for in the
- # environment; if found its value will be substituted, if not the filename will
- # be discarded.
- # rcfiles are read in the order given.
- # Values given in them will not override values given on the command line,
- # and values given in later files will not override those set in earlier
- # files, because AssignVal() will store each with a different instance index.
- # The first instance of each variable, either on the command line or in an
- # rcfile, will be stored with no instance index, and this is the value
- # normally used by programs that call this function.
- # VarNames is a comma-separated list of variable names to map to options,
- # in the same order as the options are given in OptList.
- # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
- # searched for in the environment. If set to -1, all values will be searched
- # for in the environment. Values given in the environment will override
- # those given in the rcfiles but not those given on the command line.
- # NoRCopt, if given, is an additional letter option that if given on the
- # command line prevents the rcfiles from being read.
- # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
- # ExclusiveOptions() for a description of exOpts.
- # Special options:
- # If x is made an option and is given, some debugging info is output.
- # h is assumed to be the help option.
-
- # Global variables:
- # The command line arguments are taken from ARGV[].
- # The arguments that are option specifiers and values are removed from
- # ARGV[], leaving only ARGV[0] and the non-option arguments.
- # The number of elements in ARGV[] should be in ARGC.
- # After processing, ARGC is set to the number of elements left in ARGV[].
- # The option values are put in Options[].
- # On error, Err is set to a positive integer value so it can be checked for in
- # an END block.
- # Return value: The number of elements left in ARGV is returned.
- # Must keep OptErr global since it may be set by InitOpts().
- function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
- AllowUnrecOpt,optChars,exOpts, ArgsLeft,e) {
- if (MinArgs == "")
- MinArgs = 0
- ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
- optChars)
- if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
- if (ArgsLeft >= 0) {
- OptErr = "Not enough arguments"
- Err = 4
- }
- else
- Err = -ArgsLeft
- printf "%s: %s.\nUse -h for help.\n%s\n",
- Name,OptErr,Usage > "/dev/stderr"
- exit 1
- }
- if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
- (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
- {
- print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
- Err = -e
- exit 1
- }
- if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
- {
- printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
- Err = 1
- exit 1
- }
- return ArgsLeft
- }
-
- # ReadConfFile(): Read a file containing var/value assignments, in the form
- # <variable-name><assignment-char><value>.
- # Whitespace (spaces and tabs) around a variable (leading whitespace on the
- # line and whitespace between the variable name and the assignment character)
- # is stripped. Lines that do not contain an assignment operator or which
- # contain a null variable name are ignored, other than possibly being noted in
- # the return value. If more than one assignment is made to a variable, the
- # first assignment is used.
- # Input variables:
- # File is the file to read.
- # Comment is the line-comment character. If it is found as the first non-
- # whitespace character on a line, the line is ignored.
- # Assign is the assignment string. The first instance of Assign on a line
- # separates the variable name from its value.
- # If StripWhite is true, whitespace around the value (whitespace between the
- # assignment char and trailing whitespace on the line) is stripped.
- # VarPat is a pattern that variable names must match.
- # Example: "^[a-zA-Z][a-zA-Z0-9]+$"
- # If FlagsOK is true, variables are allowed to be "set" by being put alone on
- # a line; no assignment operator is needed. These variables are set in
- # the output array with a null value. Lines containing nothing but
- # whitespace are still ignored.
- # Output variables:
- # Values[] contains the assignments, with the indexes being the variable names
- # and the values being the assigned values.
- # Lines[] contains the line number that each variable occured on. A flag set
- # is record by giving it an index in Lines[] but not in Values[].
- # Return value:
- # If any errors occur, a string consisting of descriptions of the errors
- # separated by newlines is returned. In no case will the string start with a
- # numeric value. If no errors occur, the number of lines read is returned.
- function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
- FlagsOK,
- Line,Status,Errs,AssignLen,LineNum,Var,Val) {
- if (Comment != "")
- Comment = "^" Comment
- AssignLen = length(Assign)
- if (VarPat == "")
- VarPat = "." # null varname not allowed
- while ((Status = (getline Line < File)) == 1) {
- LineNum++
- sub("^[ \t]+","",Line)
- if (Line == "") # blank line
- continue
- if (Comment != "" && Line ~ Comment)
- continue
- if (Pos = index(Line,Assign)) {
- Var = substr(Line,1,Pos-1)
- Val = substr(Line,Pos+AssignLen)
- if (StripWhite) {
- sub("^[ \t]+","",Val)
- sub("[ \t]+$","",Val)
- }
- }
- else {
- Var = Line # If no value, var is entire line
- Val = ""
- }
- if (!FlagsOK && Val == "") {
- Errs = Errs \
- sprintf("\nBad assignment on line %d of file %s: %s",
- LineNum,File,Line)
- continue
- }
- sub("[ \t]+$","",Var)
- if (Var !~ VarPat) {
- Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
- LineNum,File,Var)
- continue
- }
- if (!(Var in Lines)) {
- Lines[Var] = LineNum
- if (Pos)
- Values[Var] = Val
- }
- }
- if (Status)
- Errs = Errs "\nCould not read file " File
- close(File)
- return Errs == "" ? LineNum : substr(Errs,2) # Skip first newline
- }
-
- # Variables:
- # Data is stored in Options[].
- # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
- # Global vars:
- # Sets OptErr. Uses ENVIRON[].
- # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
- function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
- Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
- fNames,numrcFiles,filesRead,Err,Values,retStr) {
- split("",filesRead,"") # make awk know this is an array
- NumVars = split(VarNames,Vars,",")
- TypesInd = Ret = 0
- if (EnvSearch == -1)
- EnvSearch = NumVars
- for (i = 1; i <= NumVars; i++) {
- Var = Vars[i]
- CharOpt = substr(OptList,++TypesInd,1)
- if (CharOpt ~ "^[:;*()#<>&]$")
- CharOpt = substr(OptList,++TypesInd,1)
- Map[Var] = CharOpt
- Types[Var] = Type = substr(OptList,TypesInd+1,1)
- # Do not overwrite entries from environment
- if (i <= EnvSearch && Var in ENVIRON &&
- (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
- return Err
- }
-
- numrcFiles = split(rcFiles,fNames,":")
- for (i = 1; i <= numrcFiles; i++) {
- rcFile = fNames[i]
- if (rcFile ~ "^~/")
- rcFile = ENVIRON["HOME"] substr(rcFile,2)
- else if (rcFile ~ /^\$/) {
- rcFile = substr(rcFile,2)
- match(rcFile,"^[a-zA-Z0-9_]*")
- envvar = substr(rcFile,1,RLENGTH)
- if (envvar in ENVIRON)
- rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
- else
- continue
- }
- if (rcFile in filesRead)
- continue
- # rcfiles are liable to be given more than once, e.g. UHOME and HOME
- # may be the same
- filesRead[rcFile]
- if ("x" in Options)
- printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
- retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
- if (retStr > 0)
- READ_RCFILE = 1
- else if (ret != "") {
- OptErr = retStr
- Ret = -1
- }
- for (Var in Lines)
- if (Var in Map) {
- if ((Err = AssignVal(Map[Var],
- Var in Values ? Values[Var] : "",Options,Types[Var],
- Var in Values,Var,0)) < 0)
- return Err
- }
- else {
- OptErr = sprintf(\
- "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
- Lines[Var],rcFile)
- Ret = -1
- }
- }
-
- if ("x" in Options)
- for (Var in Map)
- if (Map[Var] in Options)
- printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
- "/dev/stderr"
- else
- printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
- return Ret
- }
-
- # OptSets is a semicolon-separated list of sets of option sets.
- # Within a list of option sets, the option sets are separated by commas. For
- # each set of sets, if any option in one of the sets is in Options[] AND any
- # option in one of the other sets is in Options[], an error string is returned.
- # If no conflicts are found, nothing is returned.
- # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
- # the exclusions presented by the first set of sets (ab,def,g) if:
- # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
- # (a or b is in Options[]) AND (g is in Options) OR
- # (d, e, or f is in Options[]) AND (g is in Options)
- # An error will be returned due to the exclusions presented by the second set
- # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
- # todo: make options given on command line unset options given in config file
- # todo: that they conflict with.
- function ExclusiveOptions(OptSets,Options,
- Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
- SetNum,OSetNum) {
- NumSetSets = split(OptSets,SetSets,";")
- # For each set of sets...
- for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
- # NumSets is the number of sets in this set of sets.
- NumSets = split(SetSets[SetSet],Sets,",")
- # For each set in a set of sets except the last...
- for (SetNum = 1; SetNum < NumSets; SetNum++) {
- s1 = Sets[SetNum]
- L1 = length(s1)
- for (Pos1 = 1; Pos1 <= L1; Pos1++)
- # If any of the options in this set was given, check whether
- # any of the options in the other sets was given. Only check
- # later sets since earlier sets will have already been checked
- # against this set.
- if ((c1 = substr(s1,Pos1,1)) in Options)
- for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
- s2 = Sets[OSetNum]
- L2 = length(s2)
- for (Pos2 = 1; Pos2 <= L2; Pos2++)
- if ((c2 = substr(s2,Pos2,1)) in Options)
- ErrStr = ErrStr "\n"\
- sprintf("Cannot give both %s and %s options.",
- c1,c2)
- }
- }
- }
- if (ErrStr != "")
- return substr(ErrStr,2)
- return ""
- }
-
- # The value of each instance of option Opt that occurs in Options[] is made an
- # index of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Set(Options,Opt,Set, count) {
- if (!(Opt in Options))
- return 0
- Set[Options[Opt]]
- count = Options[Opt,"count"]
- for (; count > 1; count--)
- Set[Options[Opt,count]]
- return count
- }
-
- # The value of each instance of option Opt that occurs in Options[] that
- # begins with "!" is made an index of nSet[] (with the ! stripped from it).
- # Other values are made indexes of Set[].
- # The return value is the number of instances of Opt in Options.
- function Opt2Sets(Options,Opt,Set,nSet, count,aSet,ret) {
- ret = Opt2Set(Options,Opt,aSet)
- for (value in aSet)
- if (substr(value,1,1) == "!")
- nSet[substr(value,2)]
- else
- Set[value]
- return ret
- }
-
- # Returns true if option Opt was given on the command line.
- function CmdLineOpt(Options,Opt, i) {
- for (i = 1; (Opt,"num",i) in Options; i++)
- if (Options[Opt,"num",i] != 0)
- return 1
- return 0
- }
- ### End of ProcArgs library
- ### Begin array routines
-
- # InitArr: Initialize an array with values.
- # Ind and Vals are separated into lists on Sep.
- # For each item in Ind, an index with that name is created in Arr[],
- # and the value with the same position in Vals is stored in it.
- # Global variables: none.
- function InitArr(Arr,Ind,Vals,sep, numind,indnames,values) {
- split(Ind,indnames,sep)
- split(Vals,values,sep)
- for (numind in indnames)
- Arr[indnames[numind]] = values[numind]
- }
-
- function ClearArr(Arr, Elem) {
- for (Elem in Arr)
- delete Arr[Elem]
- }
-
- function CopyArr(From,To, Elem) {
- for (Elem in From)
- To[Elem] = From[Elem]
- }
-
- # Subtract the values in Subtrahend from those in Minuend
- function SubtractArr(Minuend,Subtrahend, Elem) {
- for (Elem in Subtrahend)
- Minuend[Elem] -= Subtrahend[Elem]
- }
- # For each element of the array In, an element is created in Out having
- # an index equal to the value of the element in In and a value equal to
- # the index of the element in In.
- function Invert(In,Out, Index) {
- for (Index in In)
- Out[In[Index]] = Index
- }
-
- # Assign: make an array from a list of assignments.
- # An index with the name of each variable in the list is created in the array.
- # Its value is set to the value given for it.
- # Input variables:
- # Elements is a string containing the list of variable-value pairs.
- # Sep is the string that separates the pairs in the list.
- # AssignOp is the string that separates variables from values.
- # Output variables:
- # Arr is the array.
- # Return value: the number of elements added to the set.
- # Example:
- # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
- function Assign(Arr,Elements,Sep,AssignOp,
- Num,Names,Elem,Assignments,Assignment,i) {
- Num = split(Elements,Assignments,Sep)
- for (i = 1; i <= Num; i++) {
- Assignment = Assignments[i]
- Ind = index(Assignment,AssignOp)
- Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
- }
- return Num
- }
-
- # Packs Arr[], which should have integer indices starting at or above n, to
- # contiguous integer indices starting with n.
- # If n is not given it defaults to 0.
- # Num should be the number of elements in Arr.
- function PackArr(Arr,Num,n, NewInd,OldInd) {
- NewInd = OldInd = n+0
- for (; Num; Num--) {
- while (!(OldInd in Arr))
- OldInd++
- if (NewInd != OldInd) {
- Arr[NewInd] = Arr[OldInd]
- delete Arr[OldInd]
- }
- OldInd++
- NewInd++
- }
- }
- ### End array routines
- ### Begin set library
- # 96/05/23 added return values jhdiii
- # 96/05/25 added set2list()
-
- # Return value: the number of new elements added to Inter
- function Intersection(A,B,Inter, Elem,Count) {
- for (Elem in A)
- if (Elem in B && !(Elem in Inter)) {
- Inter[Elem]
- Count++
- }
- return Count
- }
-
- # Return value: the number of new elements added to Both
- function Union(A,B,Both) {
- return CopySet(A,Both) + CopySet(B,Both)
- }
-
- # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
- # Return value: the number of elements deleted.
- function SubtractSet(Minuend,Subtrahend, Elem,nDel) {
- for (Elem in Subtrahend)
- if (Elem in Minuend) {
- delete Minuend[Elem]
- nDel++
- }
- return nDel
- }
-
- # Return value: the number of new elements added to To
- function CopySet(From,To, Elem,n) {
- for (Elem in From)
- if (!(Elem in To)) {
- To[Elem]
- n++
- }
- return n
- }
-
- # Returns 1 if Set is empty, 0 if not.
- function IsEmpty(Set, i) {
- for (i in Set)
- return 0
- return 1
- }
-
- # MakeSet: make a set from a list.
- # An index with the name of each element of the list is created in the given
- # array.
- # Input variables:
- # Elements is a string containing the list of elements.
- # Sep is the character that separates the elements of the list.
- # Output variables:
- # Set is the array.
- # Return value: the number of new elements added to the set.
- function MakeSet(Set,Elements,Sep, i,Num,Names,nFound,ind) {
- nFound = 0
- Num = split(Elements,Names,Sep)
- for (i = 1; i <= Num; i++) {
- ind = Names[i]
- if (!(ind in Set)) {
- Set[ind]
- nFound++
- }
- }
- return nFound
- }
-
- # Returns the number of elements in set Set
- function NumElem(Set, elem,Num) {
- for (elem in Set)
- Num++
- return Num
- }
-
- # Remove all elements from Set
- function DeleteAll(Set, i) {
- split("",Set,",")
- }
-
- # Returns a list of all of the elements in Set[], with each pair of elements
- # separated by Sep.
- function set2list(Set,Sep, list,elem) {
- for (elem in Set)
- list = list Sep elem
- return substr(list,2) # skip 1st separator
- }
- ### End set library
- ### Begin timedate routines.
- # These functions operate on absolute dates & times.
-
- # convert month/day or year/month/day date to yymmdd date
- # uses global "year" var if year not given
- function makedate(InDate,Elements,d,date) {
- Elements = split(InDate,d,"/")
- date = d[1] * 100 + d[2]
- if (Elements == 2)
- date += year
- else if (Elements == 3)
- date = date * 100 + d[3]
- else
- return -1
- return date
- }
-
- # convert yymmdd date to yy/mm/dd date
- function unmakedate(Date) {
- return substr(Date,1,2) "/" substr(Date,3,2) "/" substr(Date,5,2)
- }
-
- function MkMonth2Num( Month) {
- split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",Months,",")
- for (Month in Months)
- Month2Num[Months[Month]] = sprintf("%02d",Month)
- }
-
- # Takes a date and stores its components in the following elements of Date[]:
- # year yy
- # month mm
- # day dd
- # hour hh
- # min mm
- # The following are set in Date[] if given:
- # tz TTT|offset
- # lyear yyyy
- # weekday Www
- # sec ss
-
- # On success, the date in touch/date/etc. format (MMddhhmmyy) is returned.
- # On failure, a negative value is returned.
-
- # InDate form:
- # [Weekday[,] (Month [d]d)|([d]d Month) TZ|time|year TZ|time|year [TZ|time|year]
- # where Www is a weekday name that starts with a recognized 3-char prefix,
- # Month is a month name that starts with a recognized 3-char prefix,
- # [d]d is a day of the month,
- # TZ is a timezone name (three upper case alpha chars) or offset from GMT
- # as [-+]NNNN, time is of the form [h]h:mm[:ss],
- # and year is of the form [cc]nn where cc is >= 19.
- # Common patterns for a particular date:
- # [Tue[,]] Jun 12 11:02:46 [BST|-0800] [19]90
- # [Tue[,]] 12 Jun [19]90 11:02:46 [BST|-0800]
- function ParseDate(InDate,Date, El,MonthName,TimeEl,i,Months,Ind,Num) {
- if (!("Jan" in Month2Num)) {
- MkMonth2Num()
- split("month,day,hour,min,year",TouchParts,",")
- }
- # Clear Date[]
- split("",Date," ")
- Num = split(InDate,El," +")
- if (!(4 <= Num && Num <= 6))
- return -1
- Ind = 1
- if (El[Ind] ~ "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*,?") {
- Date["weekday"] = substr(El[Ind],1,3)
- Ind++
- }
- if ((El[Ind] + 0) > 0) {
- Date["day"] = sprintf("%02d",El[Ind++])
- MonthName = substr(El[Ind++],1,3)
- }
- else {
- MonthName = substr(El[Ind++],1,3)
- Date["day"] = sprintf("%02d",El[Ind++])
- }
- if ((Date["day"] + 0 > 31) || (Date["day"] + 0 < 1))
- return -5
- if (!(MonthName in Month2Num))
- return -3
- Date["month"] = Month2Num[MonthName]
- for (; Ind <= Num; Ind++) {
- if ((El[Ind] ~ "^[0-2]?[0-9]:[0-5][0-9](:[0-5][0-9])?$") && \
- !("hour" in Date)) {
- split(El[Ind],TimeEl,":")
- Date["hour"] = sprintf("%02d",TimeEl[1])
- Date["min"] = TimeEl[2]
- if (3 in TimeEl)
- Date["sec"] = TimeEl[3]
- }
- else if ((El[Ind] ~ "^[1-9][0-9][0-9][0-9]$") && !("year" in Date)) {
- Date["year"] = substr(El[Ind],3)
- Date["lyear"] = El[Ind]
- }
- else if ((El[Ind] ~ "^[0-9][0-9]$") && !("year" in Date))
- Date["year"] = El[Ind]
- else if ((El[Ind] ~ "^([A-Z][A-Z][A-Z])|([-+][0-2][0-9][0-5][0-9])$") \
- && !("tz" in Date))
- Date["tz"] = El[Ind]
- else
- return -2
- }
- for (i in TouchParts)
- if (!(TouchParts[i] in Date))
- return -i - 5
- touchdate = Date["month"] Date["day"] Date["hour"] Date["min"] Date["year"]
- return touchdate
- }
-
- # Convert a file timestamp as printed by 'l' to an epoch time.
- # noTZ should be 1 if the date output was produced with a 0 TZ; in this case
- # no attempt is made to undo the TZ adjustment.
- function lDate2unixtime(Mon,Day,Year,noTZ, Month) {
- if (!(1 in Month2Num)) {
- MkMonth2Num()
- CurYear = strftime("%y")
- CurMonth = strftime("%m")
- }
- Month = Month2Num[Mon]
- # Deal with varying dates printed by l
- # Use year if given
- # Subtract 1 from year if month given is from last year
- if (Year ~ ":") # If year is actually time...
- Year = (CurYear - (Month > CurMonth)) % 100
- return date2unixtime(Year,Month,Day,noTZ)
- }
-
- # Returns the number of seconds that passed from 1970 Jan 1 00:00:00
- # to the given date.
- # Timezone should be given as a numeric offset from GMT in seconds.
- # Use 0 for Timezone if the date being converted was not generated with a
- # timezone adjustment.
- function unixtime(Year,Month,Day,Hour,Minute,Second,Timezone) {
- return ((YMD2day(Year,Month,Day) * 24 + Hour) * 60 + Minute) * 60 + \
- Second + Timezone
- }
-
- # date2unixtime returns the number of seconds that passed from
- # 1970 Jan 1 00:00:00 GMT to the given date, which is assumed to be in the
- # local timezone. Note that if the given date occured in daylight savings
- # time and the current time (which is used to calculate TZOffset) is not,
- # or vice versa, this will be off by the DST shift.
- # If noTZ is set, the date is taken to be in GMT and TZ modifications are not
- # done.
- # Globals: Sets/uses TZOffset and MDays[].
- function date2unixtime(Year,Month,Day,noTZ, LeapDays) {
- if (!noTZ && TZOffset == "")
- MakeTZOffset()
- if (Year > 100)
- Year -= 1900
- LeapDays = int((Year - 68) / 4)
- if (Month <= 2 && Year % 4 == 0)
- LeapDays -= 1
- if (!MDays[2])
- split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
- return ((Year - 70) * 365 + MDays[Month + 0] + Day - 1 + LeapDays) \
- * 24 * 3600 - (noTZ ? 0 : TZOffset)
- }
-
- # Sets global TZOffset to the number of seconds that need to be substracted
- # from the local date (without time of day) to give an epoch time.
- # TZOffset can also be added to systime() before doing %86400 to get the
- # current day number in the local timezone.
- # Note that TZOffset is only correct if the given date is in the same DST
- # phase as the current date.
- # 95/03/26 Calculate TZOffset more accurately.
- function MakeTZOffset( t) {
- t = systime()
- TZOffset = strftime("%H",t)*3600+strftime("%M",t)*60+strftime("%S",t) - \
- t%86400
- if (strftime("%j",0) != "001") # If TZ offset > 0
- TZOffset -= 24*3600
- }
-
- # Convert a numeric timezone to a number of seconds.
- # Example: converts -0830 to -30600
- function TZ2sec(NTimezone) {
- if (NTimezone < 0) {
- NTimezone = substr(NTimezone,2)
- Mult = -1
- }
- else
- Mult = 1
- return (substr(NTimezone,1,2)*3600+substr(NTimezone,3,2)*60)*Mult
- }
-
- # Only works for current time... does *not* take a systime argument!
- function my_strftime(Format, Time) {
- "date \"+" Format "\"" | getline Time
- return Time
- }
-
- ### End timedate routines
- ### Begin epochdays routines.
- # These functions operate on epoch days and epoch months, which have the same 0
- # time as UNIX epoch seconds. These functions are mainly used to avoid having
- # to deal with timezone issues.
- # @(#) epochdays 1.1 95/08/26
-
- # YMD2day(year,month,day-of-month) returns the number of days that passed from
- # 1970 Jan 1 to the given date.
- # All parameters should be given in numeric form.
- # If year < 70, it is assumed to be part of the 2000 century
- # If year in (70..99), it is assumed to be part of the 1900 century.
- # Globals: sets and uses MDays[]
- function YMD2day(Year,Month,Day, LeapDays) {
- Year+=0
- Month+=0
- if (Year < 70)
- Year += 100
- else if (Year >= 100)
- Year -= 1900
- # Year is now the number of years since 1900.
- LeapDays = int((Year - 68) / 4)
- if (Month <= 2 && Year % 4 == 0)
- LeapDays -= 1
- if (!(0 in MDays))
- split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
- return (Year - 70) * 365 + MDays[Month] + Day - 1 + LeapDays
- }
-
- # date2day("yy/mm/dd") returns the number of days that passed from
- # 1970 Jan 1 to the given date. -1 is returned on error.
- # The fields are returned in Fields: year in Fields[1], month in Fields[2],
- # and day (if given) in Fields[3].
- function date2day(Date,Fields, Num,Year,Month) {
- Num = split(Date,Fields,"/")
- if (Num != 2 && Num != 3)
- return -1
- if (!(Year = Fields[1] + 0) || !(Month = Fields[2] + 0))
- return -1
- if (Num == 3)
- Day = Fields[3]
- return YMD2day(Year,Month,Day)
- }
-
- # diffdays(year1,month1,day-of-month1,year2,month2,day-of-month2)
- # returns the number of complete days that passed from date 1 to date 2
- function diffdays(year1,month1,day1,year2,month2,day2) {
- return YMD2day(year2,month2,day2) - YMD2day(year1,month1,day1)
- }
-
- # Given an epoch month, return the first day of that month
- function month2day(Month) {
- return YMD2day(int(Month/12) + 1970,Month % 12 + 1,1)
- }
-
- # Given an epoch day, returns epoch month
- function day2month(Day, Date) {
- day2YMD(Day,Date)
- return (Date["y"]-1970)*12 + Date["m"]-1
- }
-
- # Given an epoch month, returns the number of days in that month.
- function monthdays(month, year) {
- if (!(0 in MDur))
- split("31 28 31 30 31 30 31 31 30 31 30 31",MDur)
- year = int(month/12)
- month = month%12+1
- return (!((year+2)%4) && month == 2) ? 29 : MDur[month]
- }
-
- # Given an epoch day (day since 1970 Jan 1; day 0 = 1970 Jan 1, etc.),
- # returns the date elements in Date:
- # Date["y"] = year (4 digits), Date["m"] = month (jan = 1, etc.),
- # Date["d"] = day of month.
- # Globals: Sets/uses MDays[].
- function day2YMD(Day,Date, QYears,Year,NonLeapYears,Month) {
- if (!(0 in LDays)) {
- split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
- split("0 31 60 91 121 152 182 213 244 274 305 335 366",LDays," ")
- }
- Day += 365
- # Day is now # of days since Jan 1 1969. 1968 was a leap year.
- QYears = int(Day / (365*4+1))
- Year = 1969 + QYears * 4
- Day -= QYears * (365*4+1)
- # Day now contains no complete leap years.
- Year += NonLeapYears = int(Day/365)
- Leap = !(Year % 4)
- Day -= NonLeapYears * 365
- # Day now contains the day of year.
- # Find the month. Divide day by 32 to get either the correct month or
- # the month prior to it.
- Month = int(Day++ / 32) + 1
- if (Day > (Leap ? LDays[Month+1] : MDays[Month+1]))
- Month++
- Day -= Leap ? LDays[Month] : MDays[Month]
- Date["d"] = Day
- Date["m"] = Month
- Date["y"] = Year
- }
-
- # Given a month number, return a date in the form yy/mm
- function month2date(MonthNum) {
- return sprintf("%02d/%02d",(MonthNum / 12 + 70) % 100, MonthNum % 12 + 1)
- }
-
- # Given a day number, return a date in the form yy/mm/dd
- function day2date(day) {
- day2YMD(day,Date)
- return sprintf("%02d/%02d/%02d",Date["y"]%100,Date["m"],Date["d"])
- }
-
- ### End epochdays routines
- ### Begin qsort routines
-
- # Arr[] is an array of values with arbitrary indices.
- # k[] is returned with numeric indices 1..n.
- # The values in k[] are the indices of Arr[],
- # ordered so that if Arr[] is stepped through
- # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
- # through in order of the values of its elements.
- # The return value is the number of elements in the arrays (n).
- function qsortArbIndByValue(Arr,k, ArrInd,ElNum) {
- ElNum = 0
- for (ArrInd in Arr)
- k[++ElNum] = ArrInd
- qsortSegment(Arr,k,1,ElNum)
- return ElNum
- }
-
- # Sort a segment of an array.
- # Arr[] contains data with arbitrary indices.
- # k[] has indices 1..nelem, with the indices of arr[] as values.
- # This function sorts the elements of arr that are pointed to by
- # k[start..end], swapping the values of elements of k[] so that
- # when this function returns arr[k[start..end]] will be in order.
- function qsortSegment(Arr,k,start,end, left,right,sepval,tmp,tmpe,tmps) {
- # handle two-element case explicitly for a tiny speedup
- if ((end - start) == 1) {
- if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
- k[start] = tmpe
- k[end] = tmps
- }
- return
- }
- # Make sure comparisons act on these as numbers
- left = start+0
- right = end+0
- sepval = Arr[k[int((left + right) / 2)]]
- # Make every element <= sepval be to the left of every element > sepval
- while (left < right) {
- while (Arr[k[left]] < sepval)
- left++
- while (Arr[k[right]] > sepval)
- right--
- if (left < right) {
- tmp = k[left]
- k[left++] = k[right]
- k[right--] = tmp
- }
- }
- if (left == right)
- if (Arr[k[left]] < sepval)
- left++
- else
- right--
- if (start < right)
- qsortSegment(Arr,k,start,right)
- if (left < end)
- qsortSegment(Arr,k,left,end)
- }
-
- # Arr[] is an array of values with arbitrary indices.
- # k[] is returned with numeric indices 1..n.
- # The values in k are the indices of Arr[],
- # ordered so that if Arr[] is stepped through
- # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
- # through in order of the values of its indices.
- # The return value is the number of elements in the arrays (n).
- # If the indexes are numeric, Numeric should be true, so that they can be
- # compared as such rather than as strings. Numeric indexes do not have to be
- # contiguous.
- function qsortByArbIndex(Arr,k,Numeric, ArrInd,ElNum) {
- ElNum = 0
- if (Numeric)
- # Indexes do not preserve numeric type, so must be forced
- for (ArrInd in Arr)
- k[++ElNum] = ArrInd+0
- else
- for (ArrInd in Arr)
- k[++ElNum] = ArrInd
- qsortNumIndByValue(k,1,ElNum)
- return ElNum
- }
-
- # Arr is an array of elements with contiguous numeric indexes to be sorted
- # by value.
- # start and end are the starting and ending indexes of the range to be sorted.
- function qsortNumIndByValue(Arr,start,end, left,right,sepval,tmp,tmpe,tmps) {
- # handle two-element case explicitly for a tiny speedup
- if ((start - end) == 1) {
- if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
- Arr[start] = tmpe
- Arr[end] = tmps
- }
- return
- }
- left = start+0
- right = end+0
- sepval = Arr[int((left + right) / 2)]
- while (left < right) {
- while (Arr[left] < sepval)
- left++
- while (Arr[right] > sepval)
- right--
- if (left <= right) {
- tmp = Arr[left]
- Arr[left++] = Arr[right]
- Arr[right--] = tmp
- }
- }
- if (start < right)
- qsortNumIndByValue(Arr,start,right)
- if (left < end)
- qsortNumIndByValue(Arr,left,end)
- }
-
- ### End qsort routines
-